/**
 * Author: Jackin (qin.zhang@uni.lu)
 * Date: August, 2013
 * (c) 2013 University of Luxembourg - Interdisciplinary Centre for Security Reliability and Trust (SnT)
 * All rights reserved
 * GPLv3 to be found at: http://www.gnu.org/licenses/quick-guide-gplv3.html
 */

package lu.uni.geko.ext.ram;

import java.util.List;

import lu.uni.geko.common.GeKoConstants;
import lu.uni.geko.common.JoinPoint;
import lu.uni.geko.joinpointdetection.MainJoinpointDetector;
import lu.uni.geko.mapping.Pc2AvMapResolver;
import lu.uni.geko.resources.MainResourceLoader;
import lu.uni.geko.util.bridges.EMFBridge;
import lu.uni.geko.util.datastructures.N2NMap;
import lu.uni.geko.weaver.Weaver;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.util.EcoreUtil;

import ram.AttributeMapping;
import ram.ClassifierMapping;
import ram.OperationMapping;
import ram.ParameterMapping;
import ram.RamFactory;
import ram_av.Advice;
import ram_av.Ram_avFactory;
import ram_pc.Pointcut;
import ram_pc.Ram_pcFactory;
import Pc2AvMapping.Mapping;
import Pc2AvMapping.MappingEntry;
import Pc2AvMapping.Pc2AvMappingFactory;

/**
 * This class is a wrapper for the default GeKo weaver, which is responsible for processing all
 * prerequisites of the RAM aspects, before employing GeKo weaver for model weaving. These
 * prerequisites includes decomposing different compartments of the RAM aspects, creating temporary
 * base, pointcut and advice URIs as the input parameters of the GeKo weaver, etc.
 * 
 * @author Jackin
 */
public class WeaverWrapper
{
	/**
	 * Temporary URIs used for the Wrapper to save the decomposed compartments of the target RAM
	 * aspects, which is to adapt the default parameter inputs of the GeKo weaver.
	 */
	private final URI tempAdviceURI; // current temporary advice model
	private final URI tempPointcutURI; // current temporary pointcut model
	private final URI tempMappingURI; // current temporary pointcut-to-advice mapping model

	/**
	 * Constructor of the wrapper.
	 */
	public WeaverWrapper(final URI baseURI)
	{
		// Initialize necessary temporary URIs
		this.tempPointcutURI = RamUtil.createTempURI(baseURI, RamConstants.POINTCUT_FILE_EXT);
		this.tempAdviceURI = RamUtil.createTempURI(baseURI, RamConstants.ADVICE_FILE_EXT);
		this.tempMappingURI = RamUtil.createTempURI(baseURI, RamConstants.MAPPING_FILE_EXT);
	}

	/**
	 * Extension for TouchRAM model weaving. The weaving process is recursively (depth-first
	 * algorithm).<br/>
	 * 
	 * <p>
	 * The parameter is the RAM aspect which is the root of a dependency hierarchy. For example, if an
	 * aspect A depends on other aspects B and C, you just need to use the URI of aspect A as the
	 * parameter for further model weaving, because the dependent aspects (B and C) can be referred
	 * through the Instantiation compartment of the aspect A.
	 * <p/>
	 * 
	 * @param baseURI
	 *          The current base RAM aspect which is going to weave all its depending RAM aspects
	 *          recursively. The woven result is also saved in this URI.
	 */
	public final URI weave(final URI baseURI)
	{
		/*
		 * If the base RAM aspect is still depending on other RAM aspects, go recursively any leaf
		 * aspect which doesn't contain any instantiation entry. Otherwise, weave current aspect with
		 * all its dependent aspects fetched from the instantiation entries. (Depth-first algorithm)
		 */
		URI tempBaseURI = RamUtil.backup(baseURI); // current temporary base aspect
		ram.Aspect base = (ram.Aspect) MainResourceLoader.getUniqueContentRoot(tempBaseURI, "current base model");
		EList<ram.Instantiation> instantiations = base.getInstantiations();

		// if the current base RAM aspect is still depending on other aspects, then weave its all
		// depending aspects
		if (instantiations != null)
		{
			for (ram.Instantiation instantiation : instantiations)
			{ // all the aspects which the current base aspect are depending can be resolved from the
				// instantiations
				try
				{
					/*
					 * The dependent is an external aspect which the current base aspect is depending on.
					 */
					ram.Aspect dependent = (ram.Aspect) instantiation.getExternalAspect();
					URI dependentURI = EcoreUtil.getURI(dependent);

					/*
					 * According to RAM, any aspect must have exact one structural view. If the structural
					 * view is null, that means the expected dependent aspect is not found
					 */
					if (dependent.getStructuralView() == null)
						throw new RuntimeException("Cannot find the dependent RAM aspect: " + dependent.toString()
								+ ", or the expected RAM aspect doesn't contain structural view!");

					/*
					 * If the dependent aspect is OK, go recursively to weave it with all its dependent
					 * aspects
					 */
					dependentURI = weave(dependentURI);

					/**
					 * When finishing weaving the dependent aspect with all its own dependent aspects, weave
					 * this dependent aspect onto the current base aspect.
					 */
					/*
					 * Change the element names in the dependent aspect, if the instantiation type is
					 * "depends", according to RAM meta-model. The instantiation type "extends" is the default
					 * GeKo mode.
					 */
					// if
					// (instantiation.getType().getLiteral().equalsIgnoreCase(RamConstants.INSTANTIATION_TYPE_DEPENDS))
					// {
					// RamUtil.instantiate(instantiation, dependentURI);
					// MainResourceLoader.save(dependentURI);
					// }

					/*
					 * Weave current base and the dependent aspect by the compartments of RAM aspect, which is
					 * adapted for GeKo generic weaving process. The woven result is saved in the base URI.
					 */
					weaveByCompartment(tempBaseURI, dependentURI, instantiation);

				} catch (Exception ex)
				{
					ex.printStackTrace();
				}
			} // end of for
		} // end of if

		return tempBaseURI;
	}

	/**
	 * Weave the dependent RAM aspect onto the base RAM aspect. This method first decompose the RAM
	 * aspects into compartments, e.g. structural view, message view (pointcut, advice), state view
	 * (pointcut, advice), etc. Then weave the corresponding compartments split from the base and
	 * dependent aspects. The final woven model is saved in the base aspect URI.
	 * 
	 * @param baseURI
	 *          The URI of the current base RAM aspect
	 * @param dependentURI
	 *          The URI of the current dependent RAM aspect that is going to be woven
	 * @param instantiation
	 *          The current instantiation mappings (EList) between the base and dependent aspect
	 */
	private final void weaveByCompartment(URI baseURI, URI dependentURI, ram.Instantiation instantiation)
	{
		// Weave the structural view
		weaveStructuralView(baseURI, dependentURI, instantiation);
		
		// Weave the message view
	}

	private final void weaveStructuralView(URI baseURI, URI dependentURI, ram.Instantiation instantiation)
	{
		/*
		 * Get base and dependent RAM aspects
		 */
		ram.Aspect base = (ram.Aspect) MainResourceLoader.getUniqueContentRoot(baseURI, "current base aspect");
		ram.Aspect dependent = (ram.Aspect) MainResourceLoader.getUniqueContentRoot(dependentURI, "current dependent aspect");

		/*
		 * Temporary model objects
		 */
		ram_av.Advice tempAdvice = Ram_avFactory.eINSTANCE.createAdvice();
		ram_pc.Pointcut tempPointcut = Ram_pcFactory.eINSTANCE.createPointcut();
		Mapping tempMapping = Pc2AvMappingFactory.eINSTANCE.createMapping();

		/*
		 * Create advice structural view
		 */
		ram_av.StructuralView tempAdviceStructuralView = (ram_av.StructuralView) RamUtil.copyAdvice(dependent.getStructuralView());
		tempAdvice.getChildren().add(tempAdviceStructuralView);

		/*
		 * Create pointcut structural view
		 */
		ram_pc.StructuralView tempPointcutStructuralView = Ram_pcFactory.eINSTANCE.createStructuralView();
		tempPointcut.getChildren().add(tempPointcutStructuralView);

		/*
		 * To make sure the addition of the new classes in the dependent aspect's structural view, we
		 * need to ensure that the structural view is mapped in the pc2avmapping
		 */
		addMappingEntry(tempMapping, tempPointcutStructuralView, tempAdviceStructuralView);

		/*
		 * Build Pointcut and Pc2AvMapping, and instantiate the dependent aspect (fromElement)
		 */
		boolean isDepends = false;
		if (instantiation.getType().getLiteral().equalsIgnoreCase(RamConstants.INSTANTIATION_TYPE_DEPENDS))
			isDepends = true;

		for (ram.ClassifierMapping classifierMapping : instantiation.getMappings())
		{
			/*
			 * Note that the to-element is located in the base model, and the from-element is in the
			 * dependent model.
			 */
			// class mapping
			ram_pc.Class pcClass = (ram_pc.Class) RamUtil.copyPointcut(classifierMapping.getToElement());
			ram_av.Class avClass = null;
			for (ram_av.Classifier iClass : tempAdviceStructuralView.getClasses())
			{
				avClass = (ram_av.Class) iClass;
				if (avClass.getName().equalsIgnoreCase(classifierMapping.getFromElement().getName()))
					break;
			}
			avClass.setName(isDepends ? pcClass.getName() : avClass.getName());
			addMappingEntry(tempMapping, pcClass, avClass);
			tempPointcutStructuralView.getClasses().add(pcClass);
			
			// according to the RAM specification, if an aspect is woven into another aspect, its
			// public-operations should be packaged
			for (ram_av.Operation op : avClass.getOperations())
			{
				if (op.getVisibility().getValue() == ram_av.Visibility.PUBLIC_VALUE)
					op.setVisibility(ram_av.Visibility.PACKAGE);
			}

			// attribute mapping
			if (classifierMapping.getAttributeMappings() != null)
				for (ram.AttributeMapping attributeMapping : classifierMapping.getAttributeMappings())
				{
					ram_pc.Attribute pcAttribute = (ram_pc.Attribute) RamUtil.copyPointcut(attributeMapping.getToElement());
					ram_av.Attribute avAttribute = null;
					for (ram_av.Attribute iAttr : avClass.getAttributes())
					{
						avAttribute = iAttr;
						if (avAttribute.getName().equalsIgnoreCase(attributeMapping.getFromElement().getName()))
							break;
					}
					avAttribute.setName(isDepends ? pcAttribute.getName() : avAttribute.getName());
					addMappingEntry(tempMapping, pcAttribute, avAttribute);
					pcClass.getAttributes().add(pcAttribute);
				}

			// operation mapping
			if (classifierMapping.getOperationMappings() != null)
			{
				for (ram.OperationMapping operationMapping : classifierMapping.getOperationMappings())
				{
					ram_pc.Operation pcOperation = (ram_pc.Operation) RamUtil.copyPointcut(operationMapping.getToElement());
					ram_av.Operation avOperation = null;
					for (ram_av.Operation iOp : avClass.getOperations())
					{
						avOperation = iOp;
						if (avOperation.getName().equalsIgnoreCase(operationMapping.getFromElement().getName()))
							break;
					}
					avOperation.setName(isDepends ? pcOperation.getName() : avOperation.getName());
					addMappingEntry(tempMapping, pcOperation, avOperation);
					pcClass.getOperations().add(pcOperation);

					// parameter mapping
					if (operationMapping.getParameterMappings() != null)
						for (ram.ParameterMapping parameterMapping : operationMapping.getParameterMappings())
						{
							ram_pc.Parameter pcParameter = (ram_pc.Parameter) RamUtil.copyPointcut(parameterMapping.getToElement());
							ram_av.Parameter avParameter = null;
							for (ram_av.Parameter iPar : avOperation.getParameters())
							{
								avParameter = iPar;
								if (avParameter.getName().equalsIgnoreCase(parameterMapping.getFromElement().getName()))
									break;
							}
							avParameter.setName(isDepends ? pcParameter.getName() : avParameter.getName());
							addMappingEntry(tempMapping, pcParameter, avParameter);
							pcOperation.getParameters().add(pcParameter);
						}
				}
			}
		} // end of for(ClassifierMapping)

		/**
		 * Weave with base, pointcut, advice and pc2avmapping, adapted for GeKo.
		 */
		/*
		 * Save URIs as follows: base: original baseURI pointcut: this.tempPointcutURI advice:
		 * this.tempAdviceURI mapping: this.tempMappingURI
		 * 
		 * The woven model URI will be the same as the base (that's the reason we backup the base model
		 * before): woven model saved in baseURI.
		 */
		MainResourceLoader.saveEObjectAsOnlyContent(tempPointcut, this.tempPointcutURI);
		MainResourceLoader.saveEObjectAsOnlyContent(tempAdvice, this.tempAdviceURI);
		MainResourceLoader.saveEObjectAsOnlyContent(tempMapping, this.tempMappingURI);
		performWeaving(baseURI);
	}

	// one-to-one mapping entry
	private final void addMappingEntry(Mapping mapping, EObject sourceObject, EObject targetObject)
	{
		MappingEntry entry = Pc2AvMappingFactory.eINSTANCE.createMappingEntry();
		entry.getSource().add(sourceObject);
		entry.getTarget().add(targetObject);
		mapping.getEntries().add(entry);
	}

	private final void performWeaving(URI baseURI)
	{
		/**
		 * Reuse GeKo weaver.
		 */
		N2NMap<EObject, EObject> pc2AvN2NMap = (new Pc2AvMapResolver(this.tempPointcutURI, this.tempAdviceURI, this.tempMappingURI)).transform();

		if (pc2AvN2NMap == null)
			return;

		Weaver gekoWeaver = new Weaver(baseURI, this.tempAdviceURI, pc2AvN2NMap);
		URI wovenURI = gekoWeaver.copyBaseToWovenMURI();
		List<JoinPoint> joinPoints = MainJoinpointDetector.detectJoinpoints(this.tempPointcutURI, wovenURI);
		gekoWeaver.setJoinPoints(joinPoints);
		gekoWeaver.transform();

		/**
		 * Re-save the woven model in the base URI
		 */
		ram.Aspect wovenAspect = (ram.Aspect) MainResourceLoader.getUniqueContentRoot(wovenURI, "current woven model");
		MainResourceLoader.saveEObjectAsOnlyContent(wovenAspect, baseURI);
	}
}
